package com.xd.core.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xd.common.exception.XkdException;
import com.xd.common.util.CompressDownloadUtil;
import com.xd.common.util.LicensePlateValidator;
import com.xd.common.util.OrderNumUtil;
import com.xd.common.util.SecurityUtil;
import com.xd.core.business.entity.GuaranteeSlip;
import com.xd.core.business.mapper.GuaranteeSlipMapper;
import com.xd.core.business.mapper.InformationMapper;
import com.xd.core.system.service.FileService;
import com.xd.core.system.vo.ImageVO;
import com.xd.core.system.vo.UploadVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

@Service
@Slf4j
public class FileServiceImpl implements FileService {

    @Value("${image.localPathDir}")
    private String localPathDir;        // /usr/local/crmings;磁盘地址
    @Value("${image.localUrlPath}")
    private String localUrlPath;        // = "http://image.jt.com";
    @Value("${image.customerAttachment}")
    private String customerAttachment;//客户附件
    @Value("${image.headPortraitUrl}")
    private String headPortraitUrl;        // /headPortraitUrl
    @Value("${image.guaranteeSlip}")
    private String guaranteeSlip;//上传保单贷车辆信息
    @Value("${image.printed}")
    private String printed;//上传用印申请

    private final static Integer imageMaxSize = 2;//头像大小
    private final static Integer MaxSize = 20;//认证图片大小
    private final static Integer videoMaxSize = 100;//视频大小不超过100M

    @Autowired
    private GuaranteeSlipMapper guaranteeSlipMapper;
    @Autowired
    private InformationMapper informationMapper;

    //如何实现文件删除filePathfilePath
    @Override
    public void fileRemove(String virtualPath) {
        String filePath = localPathDir + virtualPath;
        File file = new File(filePath);
        //删除文件
        boolean delete = file.delete();
        log.info("删除文件：" + delete);
    }

    @Override
    public void filesRemove(List<String> virtualPaths) {
        for (String virtualPath : virtualPaths) {
            String filePath = localPathDir + virtualPath;
            File file = new File(filePath);
            boolean delete = file.delete();
            log.info("删除文件：" + delete);
        }
    }

    @Override
    public void deleteFolder(String virtualPath) {
        String filePath = localPathDir + virtualPath;
        File file = new File(filePath);
        try {
            //删除文件
            FileUtils.deleteDirectory(file);
        } catch (IOException e) {
            log.info("删除失败");
            e.printStackTrace();
        }
        log.info("删除文件成功");
    }


    @Override
    public void downloadCustomerAttachment(String path, HttpServletResponse response) {
        try {
            path = localPathDir + path;
//            path = "D:\\壁纸\\9bf5bccfa2d65114e9e0d93505e0a88e.jpeg";
            // path是指欲下载的文件的路径。
            File file = new File(path);
            // 取得文件名。
            String filename = file.getName();
            // 取得文件的后缀名。
            String ext = filename.substring(filename.lastIndexOf(".") + 1).toUpperCase();

            // 以流的形式下载文件。
            InputStream fis = new BufferedInputStream(new FileInputStream(path));
            byte[] buffer = new byte[fis.available()];
            fis.read(buffer);
            fis.close();
            // 清空response
            response.reset();
            // 设置response的Header
            String encodedFilename = URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFilename);
//            response.addHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes(), "ISO8859-1"));
            response.addHeader("Content-Length", "" + file.length());
            OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
            String mimeType = Files.probeContentType(file.toPath());
            log.info("文件类型：*" + mimeType);
            response.setContentType(mimeType != null ? mimeType : "application/octet-stream");
//            response.setContentType("application/octet-stream");
            response.addHeader("Access-Control-Allow-Origin", "*");//后端允许跨域

            toClient.write(buffer);
            toClient.flush();
            toClient.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }


    }

    @Override
    public void downloadToPack(List<String> path, HttpServletResponse response) {
        File dirFile = new File(localPathDir + customerAttachment);
        if (!dirFile.exists()) {
            dirFile.mkdirs();
        }
        List<File> files = new ArrayList<>();
        for (String s : path) {
            files.add(new File(localPathDir + s));
        }
        Integer companyId = SecurityUtil.getLoginCompanyId();
        String fileName = companyId + ".zip";
        CompressDownloadUtil.zipFiles(localPathDir + customerAttachment + "/" + fileName, files);
        downloadCustomerAttachment(customerAttachment + "/" + fileName, response);
//        //删除压缩包
        fileRemove(customerAttachment + "/" + fileName);
    }


    @Override
    public ImageVO upload(MultipartFile file, String path) throws IOException {
        //1.1校验是否为图片类型
        String fileName = file.getOriginalFilename();
        long fileBytes = file.getSize();
        double fileSize = (double) fileBytes / 1048576;
        if (fileSize <= 0) {
            throw new RuntimeException("文件内容为空");
        }

        assert fileName != null;
        fileName = fileName.toLowerCase();
        if (!fileName.matches("^.+\\.(docx|doc|pdf|xlsx|xls|jpg|png|ico|jpeg|mp4|ogg|flv|avi|wmv|rmvb|jfif|zip|bmp)$")) {
            throw new XkdException("文件格式不正确");
        }
        if (fileName.matches("^.+\\.(mp4|ogg|flv|avi|wmv|rmvb)$")) {
            if (fileSize > videoMaxSize) {
                throw new RuntimeException("视频上传内容大小超出限制100M");
            }
        }
        if (fileName.matches("^.+\\.(docx|doc|pdf|xlsx|xls|jpg|png|ico|jpeg)$")) {
            if (fileSize > MaxSize) {
                throw new RuntimeException("图片与附件上传内容大小超出限制20M");
            }
        }
        log.info("通过类型的校验");
        //1.2 校验图片类型是否为木马
        if (fileName.matches("^.+\\.(jpg|png|ico|jpeg)$")) {//如果是图片

            BufferedImage bufferedImage = null;
            try (InputStream is = file.getInputStream()) {
                bufferedImage = ImageIO.read(is);
                if (bufferedImage == null || bufferedImage.getWidth() == 0) {
                    return null;
                }
            } catch (Exception e) {
                log.error("图片解码失败: {}", fileName, e);
                return null;
            }
            int width = bufferedImage.getWidth();
            int height = bufferedImage.getHeight();
            if (width == 0 || height == 0) {
                return null;
            }
        }
        //2.实现文件上传
        String localDir = localPathDir + path;
        //2.3需要创建目录
        File dirFile = new File(localDir);
        if (!dirFile.exists()) {
            dirFile.mkdirs();
        }
//        String[] list = dirFile.list();
        String fileType = fileName.substring(fileName.lastIndexOf("."));
        fileName = fileName.substring(0, fileName.lastIndexOf(".")) + "-" + OrderNumUtil.getDateTime() + fileType;

//        if (list != null) {
//            List<String> strings = Arrays.asList(list);
//            if (CollectionUtils.isNotEmpty(strings)) {
//                int length = strings.size();
//                for (String s : strings) {//查看文件夹里是否有相同名字文件
//                    if (fileName.equals(s)) {
//                        fileName = fileName.substring(0, fileName.lastIndexOf(".")) + length + fileType;
//                        break;
//                    }
//                }
//            }
//        }
        //文件名
        String realFileName = fileName;
        //文件包含磁盘全路径
        String filePathAll = localDir + fileName;
        //不包含磁盘路径（隐藏真实全路径）
        String virtuaPath = path + realFileName;
        //图片回显地址(反向代理显示图片)
        String urlPath = localUrlPath + virtuaPath;
        //实现文件上传
        File realFile = new File(filePathAll);
        file.transferTo(realFile);
        log.info("上传文件成功");
        return new ImageVO(virtuaPath, urlPath, realFileName);
    }

    @Override
    public ImageVO uploadSinglePolicy(MultipartFile file, String path) throws IOException {
        //1.1校验是否为图片类型
        String fileName = file.getOriginalFilename();
        long fileBytes = file.getSize();
        double fileSize = (double) fileBytes / 1048576;
        if (fileSize <= 0) {
            throw new RuntimeException("文件内容为空");
        }

        assert fileName != null;
        if (!fileName.matches("^.+\\.(docx|doc|pdf|xlsx|xls|jpg|png|ico|jpeg|mp4|ogg|flv|avi|wmv|rmvb|jfif|zip|bmp)$")) {
            throw new XkdException("文件格式不正确");
        }
        if (fileName.matches("^.+\\.(mp4|ogg|flv|avi|wmv|rmvb)$")) {
            if (fileSize > videoMaxSize) {
                throw new RuntimeException("视频上传内容大小超出限制100M");
            }
        }
        if (fileName.matches("^.+\\.(docx|doc|pdf|xlsx|xls|jpg|png|ico|jpeg)$")) {
            if (fileSize > MaxSize) {
                throw new RuntimeException("图片与附件上传内容大小超出限制20M");
            }
        }
        log.info("通过类型的校验");
        //1.2 校验图片类型是否为木马
        if (fileName.matches("^.+\\.(jpg|png|ico|jpeg)$")) {//如果是图片
            BufferedImage bufferedImage = null;
            try (InputStream is = file.getInputStream()) {
                bufferedImage = ImageIO.read(is);
                if (bufferedImage == null || bufferedImage.getWidth() == 0) {
                    return null;
                }
            } catch (Exception e) {
                log.error("图片解码失败: {}", fileName, e);
                return null;
            }
            int width = bufferedImage.getWidth();
            int height = bufferedImage.getHeight();
            if (width == 0 || height == 0) {
                return null;
            }
        }

        //2.实现文件上传
        String localDir = localPathDir + path;
        //2.1 清空目标文件夹（如果存在且不为空）
        File dirFile = new File(localDir);
        //2.2 创建目录（如果不存在）
        if (!dirFile.exists()) {
            dirFile.mkdirs();
        }
        //文件名
        String realFileName = fileName;
        //文件包含磁盘全路径
        String filePathAll = localDir + fileName;
        //不包含磁盘路径（隐藏真实全路径）
        String virtuaPath = path + realFileName;
        //图片回显地址(反向代理显示图片)
        String urlPath = localUrlPath + virtuaPath;

        //实现文件上传
        File realFile = new File(filePathAll);
        file.transferTo(realFile);
        log.info("上传文件成功");
        return new ImageVO(virtuaPath, urlPath, realFileName);
    }


    /***
     * @description 批量上传保单
     * @author Zch
     * @date 15:35 2025/6/25 星期三
     * @return void
     **/
    @Override
    public List<String> uploadZipForPolicy(MultipartFile file, String incomingOrder) {
        String path = guaranteeSlip + "/" + incomingOrder + "/";
        UploadVO vo = uploadZip(file, path);
        if (CollectionUtil.isEmpty(vo.getImageVO())) {
            throw new XkdException("文件上传失败");
        }
        List<ImageVO> imageVOList = vo.getImageVO();
        //保存保单数据
        for (int i = 0; i < imageVOList.size(); i++) {
            ImageVO imageVO = imageVOList.get(i);
            // 按照 "-" 分割字符串
            String[] parts = imageVO.getFileName().split("-");
            if (parts.length == 0 || parts == null || parts.length != 2)
                throw new XkdException("批量上传保单文件未按照规则命名");
            // 提取车牌号
            String carNo = parts[0];
            //校验车牌号
            if (!LicensePlateValidator.isValidChineseLicensePlate(carNo)) {
                //删除文件
                this.fileRemove(imageVO.getVirtualPath());
                String message = StrUtil.format("车牌号有误，请仔细检查后再单独上传该车辆保单,车牌号为: {}", carNo);
                vo.getFailCause().add(message);
                continue;
            }
            //根据借款订单编号和车牌号获取保单id
            Integer guaranteeId = guaranteeSlipMapper.getGuaranteeId(carNo, incomingOrder);
            // 提取保单号
            String policyNumber = parts[1].substring(0, parts[1].length() - 4);
            LambdaQueryWrapper<GuaranteeSlip> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(GuaranteeSlip::getCarNo, carNo);
            wrapper.eq(GuaranteeSlip::getId, guaranteeId);
            wrapper.eq(GuaranteeSlip::getReviewStatus, 0);
            GuaranteeSlip guaranteeSlip = guaranteeSlipMapper.selectOne(wrapper);
            if (ObjectUtil.isEmpty(guaranteeSlip)) {
                String message = StrUtil.format("该车辆不存在保单信息或该车辆已上传保单且已复核，车牌号为: {}", carNo);
                vo.getFailCause().add(message);
                this.fileRemove(imageVO.getVirtualPath());
                continue;
            }
            guaranteeSlip.setGuaranteeSlipUrl(imageVO.getVirtualPath());
            guaranteeSlip.setUploadUser(SecurityUtil.getLoginUser().getUser().getUserName());
            guaranteeSlip.setPolicyNo(policyNumber);
            guaranteeSlip.setUploadDate(new Date());
            guaranteeSlipMapper.updateById(guaranteeSlip);
        }
        return CollectionUtil.isEmpty(vo.getFailCause()) ? null : vo.getFailCause();
    }

    /***
     * @description 上传zip文件到服务器并解压
     * @author Zch
     * @date 15:34 2025/6/25 星期三
     * @return java.util.List<com.xd.core.system.vo.ImageVO>
     **/
    public UploadVO uploadZip(MultipartFile file, String path) {
        UploadVO vo = new UploadVO();
        try {
            String contentType = file.getContentType();
            //适配不同浏览器contentType传值不同
            List<String> validMimeTypes = Arrays.asList(
                    "application/zip",
                    "application/x-zip-compressed",
                    "application/octet-stream",
                    "multipart/x-zip"
            );
            if (contentType == null || !validMimeTypes.contains(contentType)) {
                throw new XkdException("文件类型错误：只允许上传ZIP文件");
            }

            String fileName = StringUtils.cleanPath(file.getOriginalFilename());
            if (!fileName.toLowerCase().endsWith(".zip")) {
                throw new XkdException("文件扩展名错误：只允许上传.zip文件");
            }

            long maxFileSize = 50 * 1024 * 1024;
            if (file.getSize() > maxFileSize) {
                throw new XkdException("文件大小超过限制：最大允许50MB");
            }
            String localDir = localPathDir + path;//    /usr/local/xdImages/guaranteeSlip/21/
            File uploadDir = new File(localDir);
            // 1. 确保上传目录存在
            if (!uploadDir.exists()) {
                uploadDir.mkdirs();
            }

            // 2. 获取原始文件名并生成保存路径
            String filePath = localDir + fileName;//    /usr/local/xdImages/guaranteeSlip/21/a.zip

            // 3. 保存ZIP文件
            file.transferTo(new File(filePath));

            // 4. 解压ZIP文件
            String destDir = localDir;
            vo = unzip(filePath, destDir, path);

            // 5. 删除原始ZIP文件
            File zipFile = new File(filePath);
            if (zipFile.exists()) {
                boolean deleted = zipFile.delete();
                if (!deleted) {
                    throw new XkdException("文件上传并解压成功，但删除原始ZIP文件失败！");
                }
            }
            log.info("文件上传并解压成功！");
        } catch (Exception e) {
            if (e instanceof XkdException) {
                throw (XkdException) e; // 重新抛出XkdException
            }
            throw new XkdException("文件处理失败：" + e.getMessage());
        }
        return vo;
    }

    /***
     * @description 解压zip文件并将路径返回
     * @author Zch
     * @date 15:34 2025/6/25 星期三
     * @return java.util.List<com.xd.core.system.vo.ImageVO>
     **/
    private UploadVO unzip(String zipFilePath, String destDir, String path) throws IOException {
        // 初始化返回对象和失败原因列表
        UploadVO uploadVO = new UploadVO();
        uploadVO.setImageVO(new ArrayList<>());
        ArrayList<String> failCauses = new ArrayList<>();

        // 用于记录车牌号和对应的保单号
        Map<String, String> plateNumberMap = new HashMap<>();

        // 创建目标目录（如果不存在）
        File dir = new File(destDir);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        // 允许的文件扩展名（不区分大小写）
        Set<String> allowedExtensions = new HashSet<>(Arrays.asList(".jpg", ".png", ".pdf"));
        try (ZipInputStream zis = new ZipInputStream(
                new FileInputStream(zipFilePath), Charset.forName("GBK"))) {
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                String entryName = entry.getName();
                // 跳过目录条目
                if (entry.isDirectory()) {
                    log.info("跳过目录: {}", entryName);
                    zis.closeEntry();
                    continue;
                }

                // 获取文件名（不含扩展名）
                String fileName = entryName.substring(0, entryName.lastIndexOf('.'));
                // 检查文件名格式是否符合"车牌号-保单号"的格式
                if (!fileName.contains("-") || fileName.split("-").length != 2) {
                    String message = StrUtil.format("文件名格式不正确(应为车牌号-保单号): {}", entryName);
                    log.info(message);
                    failCauses.add(message);
                    zis.closeEntry();
                    continue;
                }

                // 获取车牌号和保单号
                String plateNumber = fileName.split("-")[0];
                String policyNumber = fileName.split("-")[1];

                // 检查车牌号是否已存在
                if (plateNumberMap.containsKey(plateNumber)) {
                    String existingPolicy = plateNumberMap.get(plateNumber);
                    if (!existingPolicy.equals(policyNumber)) {
                        String message = StrUtil.format("压缩包中存在重复车牌号文件: {}", plateNumber);
                        log.info(message);
                        failCauses.add(message);
                        zis.closeEntry();
                        continue;
                    }
                } else {
                    plateNumberMap.put(plateNumber, policyNumber);
                }

                // 获取文件扩展名（小写）并验证
                String extension = entryName.substring(entryName.lastIndexOf(".")).toLowerCase();
                if (!allowedExtensions.contains(extension)) {
                    String message = StrUtil.format("不符合扩展名要求的文件: {}", entryName);
                    log.info(message);
                    failCauses.add(message);
                    zis.closeEntry();
                    continue;
                }

                // 准备输出文件并检查路径安全（防止ZIP Slip攻击）
                File file = new File(destDir, entryName);
                String canonicalPath = file.getCanonicalPath();
                if (!canonicalPath.startsWith(dir.getCanonicalPath() + File.separator)) {
                    throw new IOException("非法路径: " + canonicalPath);
                }

                // 创建父目录并解压文件
                file.getParentFile().mkdirs();
                try (FileOutputStream fos = new FileOutputStream(file)) {
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = zis.read(buffer)) > 0) {
                        fos.write(buffer, 0, len);
                    }
                }

                // 构建成功解压的文件信息
                String virtuaPath = path + entryName;
                String urlPath = localUrlPath + virtuaPath;
                uploadVO.getImageVO().add(new ImageVO(virtuaPath, urlPath, entryName));
                zis.closeEntry();
            }
        }
        // 所有文件处理完成后设置失败原因
        uploadVO.setFailCause(failCauses);
        return uploadVO;
    }

    /***
     * @description 文件替换
     * @author Zch
     * @date 17:46 2025/6/25 星期三
     * @return void
     **/
    @Override
    public void replaceFile(MultipartFile file, String virtualPath, String uid) throws IOException {
        String filePath = localPathDir + virtualPath;
        File oldFile = new File(filePath);
        // 获取旧文件的名称（不带扩展名）
        String oldFileName = oldFile.getName();
        int lastDotIndex = oldFileName.lastIndexOf('.');
        String baseName = (lastDotIndex > 0) ? oldFileName.substring(0, lastDotIndex) : oldFileName;
        // 获取新文件的扩展名（如 .jpg）
        String newOriginalFilename = file.getOriginalFilename();
        String newExtension = "";
        int newFileDotIndex = newOriginalFilename.lastIndexOf('.');
        if (newFileDotIndex > 0) {
            newExtension = newOriginalFilename.substring(newFileDotIndex);
        }
        // 构建新文件名（旧文件名 + 新扩展名）
        String newFilename = baseName + newExtension;
        File newFile = new File(oldFile.getParentFile(), newFilename);
        // 删除旧文件（如果存在）
        if (oldFile.exists()) {
            boolean deleted = oldFile.delete();
            log.info("删除旧文件：" + deleted);
        }
        // 确保目录存在
        File parentDir = newFile.getParentFile();
        if (!parentDir.exists()) {
            boolean dirsCreated = parentDir.mkdirs();
            log.info("创建目录：" + dirsCreated);
        }
        // 写入新文件（直接保存，确保文件内容正确）
        try {
            file.transferTo(newFile);
            //构建新的virtualPath
            String pathWithoutExtension = virtualPath.substring(0, virtualPath.lastIndexOf("."));
            String url = pathWithoutExtension + newExtension;
            // 更新数据库中的文件名
            informationMapper.updateFileName(newFilename, url, uid);
            log.info("文件替换成功，新文件名：" + newFilename);
        } catch (IOException e) {
            log.error("文件写入失败", e);
            throw new RuntimeException("文件替换失败");
        }
    }
}
